package com.bestcem.xm.user.handler;

import cn.hutool.core.lang.Dict;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.bestcem.xm.common.core.domain.web.ServiceResult;
import com.bestcem.xm.common.core.oss.enums.OssPathEnum;
import com.bestcem.xm.component.security.dto.TokenDTO;
import com.bestcem.xm.componet.filestorage.service.StorageService;
import com.bestcem.xm.user.constant.CommonConstant;
import com.bestcem.xm.user.constant.ExcelConstant;
import com.bestcem.xm.user.constant.UserGroupConstant;
import com.bestcem.xm.user.enums.UserGroupStatusEnum;
import com.bestcem.xm.user.handler.excel.read.listener.GroupDynamicReadListener;
import com.bestcem.xm.user.handler.excel.read.listener.GroupIndexReadListener;
import com.bestcem.xm.user.mq.info.group.GroupNameInfo;
import com.bestcem.xm.user.mq.info.group.LevelNameInfo;
import com.bestcem.xm.user.mq.info.group.StoreInfo;
import com.bestcem.xm.user.mq.send.biz.UserMessageSendService;
import com.bestcem.xm.user.service.OrganizationService;
import com.bestcem.xm.user.service.UserGroupService;
import com.bestcem.xm.user.service.dto.group.UserGroupDynamicImportDTO;
import com.bestcem.xm.user.service.dto.group.UserGroupImportDTO;
import com.bestcem.xm.user.service.dto.group.UserGroupLevelDTO;
import com.bestcem.xm.user.service.dto.user.OrganizationDTO;
import com.bestcem.xm.user.util.business.ExcelUtil;
import com.bestcem.xm.user.util.business.UserGroupContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 组织导入处理器
 *
 * @author wangyunyun <yunyun.wang@idiaoyan.com>
 * @version v1.0
 * @date 2022/2/8 17:47
 */
@Slf4j
@Component
public class GroupImportHandler {

    @Resource
    private UserGroupService userGroupService;

    //@Resource
    //private UserGroupMessageSender userGroupMessageSender;

    @Resource
    private UserMessageSendService userMessageSendService;

    @Resource
    private OrganizationService organizationService;

    /*@Resource
    private OssService ossService;*/

    @Autowired
    private StorageService storageService;

    /**
     * 读取excel, 校验并返回数据
     *
     * @param inputStream
     * @param tokenDTO
     * @return
     */
    public ServiceResult<String> process(InputStream inputStream, TokenDTO tokenDTO) {
        ExcelReader excelReader = EasyExcelFactory.read(inputStream).build();
        // 读取首页sheet数据
        GroupIndexReadListener indexReadListener = readIndexSheet(excelReader);
        // 读取动态sheet, 并校验数据, 校验不通过返回错误信息
        ServiceResult<Map<String, GroupDynamicReadListener>> dynamicResult = readDynamicSheet(excelReader, indexReadListener);
        excelReader.finish();
        if (!dynamicResult.isSuccess()) {
            // 读取动态sheet失败
            ServiceResult<String> failDynamicResult = ServiceResult.fail(dynamicResult.getMsg());
            failDynamicResult.setErrorCode(dynamicResult.getErrorCode());

            // 写错误文件
            if (dynamicResult.getData() != null) {
                String errorFilePath = writeErrorFile(indexReadListener, dynamicResult.getData(), tokenDTO.getOrgId());
                failDynamicResult.setData(errorFilePath);
            }
            return failDynamicResult;
        }

        //  excel文件格式校验通过, 处理并封装具体数据
        List<UserGroupImportDTO> importDTOS = handleGroupExcelData(indexReadListener, dynamicResult.getData());

        // 保存层级和组织
        ServiceResult<String> saveResult = saveGroupTree(indexReadListener.getLevelNames(), importDTOS, tokenDTO);

        if (!saveResult.isSuccess()) {
            // 保存层级和组织失败
            return ServiceResult.fail(saveResult.getMsg());
        }

        return ServiceResult.success();
    }

    /**
     * 保存层级和组织
     *
     * @param levelNames
     * @param importDTOS
     * @param tokenDTO
     * @return
     */
    public ServiceResult<String> saveGroupTree(Map<Integer, String> levelNames,
                                               List<UserGroupImportDTO> importDTOS, TokenDTO tokenDTO) {

        // 设置层级参数
        List<UserGroupLevelDTO> levelDTOS = new ArrayList<>();
        for (Map.Entry<Integer, String> entry : levelNames.entrySet()) {
            Integer levelNum = entry.getKey();
            UserGroupLevelDTO levelDTO = new UserGroupLevelDTO();
            levelDTO.setLevelNum(levelNum);
            levelDTO.setGroupLevelName(levelNames.get(levelNum));
            levelDTOS.add(levelDTO);
        }
        levelDTOS = levelDTOS.stream().sorted(Comparator.comparingInt(UserGroupLevelDTO::getLevelNum)).collect(Collectors.toList());

        ServiceResult<String> saveResult = userGroupService.saveGroupAndLevel(levelDTOS, importDTOS, tokenDTO);

        if (!saveResult.isSuccess()) {
            // 保存层级和组织失败
            return ServiceResult.fail(saveResult.getMsg());
        }
        // 保存层级和组织成功, 发送mq
        String rootGroupId = saveResult.getData();
        sendMq(tokenDTO, rootGroupId);

        return ServiceResult.success();
    }

    /**
     * 上传组织文件成功, 发送mq
     *
     * @param tokenDTO
     * @param rootGroupId
     */
    private void sendMq(TokenDTO tokenDTO, String rootGroupId) {
        // 发送消息: 组织失效
        List<String> invalidGroups = UserGroupContextUtil.getInvalidGroups();
        if (CollectionUtils.isNotEmpty(invalidGroups)) {
            // 分批发送批量消息
            List<List<String>> batch = ListUtils.partition(invalidGroups, CommonConstant.MQ_PARTITION_SIZE);
            for (List<String> list : batch) {
                userMessageSendService.publishGroupInvalidateList(tokenDTO.getOrgId(), list);
            }
        }

        // 发送消息: 组织名称变更
        List<GroupNameInfo> updateNameGroups = UserGroupContextUtil.getUpdateNameGroups();
        if (CollectionUtils.isNotEmpty(updateNameGroups)) {
            // 分批发送批量消息
            List<List<GroupNameInfo>> batch = ListUtils.partition(updateNameGroups, CommonConstant.MQ_PARTITION_SIZE);
            for (List<GroupNameInfo> list : batch) {
                userMessageSendService.publishGroupTitleModifyList(tokenDTO.getOrgId(), list);
            }
        }

        // 发送消息: 组织恢复失效
        List<String> resumeGroups = UserGroupContextUtil.getResumeGroups();
        if (CollectionUtils.isNotEmpty(resumeGroups)) {
            // 分批发送批量消息
            List<List<String>> batch = ListUtils.partition(resumeGroups, CommonConstant.MQ_PARTITION_SIZE);
            for (List<String> list : batch) {
                userMessageSendService.publishGroupValidateList(tokenDTO.getOrgId(), list);
            }
        }

        // 发送消息: 批量新增门店
        List<StoreInfo> addStoreGroups = UserGroupContextUtil.getAddStoreGroups();
        if (CollectionUtils.isNotEmpty(addStoreGroups)) {
            // 分批发送批量消息
            List<List<StoreInfo>> batch = ListUtils.partition(addStoreGroups, CommonConstant.MQ_PARTITION_SIZE);
            for (List<StoreInfo> list : batch) {
                userMessageSendService.publishCreateStoreList(tokenDTO.getOrgId(), rootGroupId, list);
            }
        }

        // 发送消息: 删除层级
        List<String> delLevelIds = UserGroupContextUtil.getDelLevelId();
        if (CollectionUtils.isNotEmpty(delLevelIds)) {
            for (String delLevelId : delLevelIds) {
                userMessageSendService.publishGroupLevelDelete(tokenDTO.getOrgId(), delLevelId);
            }
        }

        // 发送消息: 层级名称变更
        List<LevelNameInfo> updateNameLevels = UserGroupContextUtil.getUpdateNameLevels();
        if (CollectionUtils.isNotEmpty(updateNameLevels)) {
            for (LevelNameInfo levelNameInfo : updateNameLevels) {
                userMessageSendService.publishGroupLevelTitleModify(tokenDTO.getOrgId(), levelNameInfo.getLevelId(),
                        levelNameInfo.getOldLevelName(), levelNameInfo.getNewLevelName());
            }
        }

        // 发送消息: 组织发生变化
        userMessageSendService.publishGroupsChange(tokenDTO.getOrgId(), rootGroupId);

        // 清除ThreadLocal
        UserGroupContextUtil.remove();
    }

    /**
     * 上传错误文件
     *
     * @param indexReadListener      首页sheet
     * @param dynamicReadListenerMap 动态sheet
     * @param orgId
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @date 2022/2/18 07:49
     */
    private String writeErrorFile(GroupIndexReadListener indexReadListener,
                                  Map<String, GroupDynamicReadListener> dynamicReadListenerMap, String orgId) {
        // 设置写入流
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ExcelWriter excelWriter = EasyExcelFactory.write(outputStream).build();
        // 设置填写须知sheet
        ExcelUtil.buildGroupIntroduceSheet(excelWriter);
        // 写首页sheet数据
        // 设置表头
        List<List<String>> head = new ArrayList<>();
        int headerSize = indexReadListener.getHeaders().size();
        for (int i = 0; i < headerSize; i++) {
            String header = indexReadListener.getHeaders().get(i);
            head.add(Collections.singletonList(header));
        }
        head.add(Collections.singletonList(UserGroupConstant.GROUP_INDEX_SHEET_ERROR_ROW_TITLE));
        // 设置首页sheet
        WriteSheet indexSheet = EasyExcelFactory.writerSheet(indexReadListener.getSheetName())
                .head(head)
                .registerWriteHandler(ExcelUtil.buildGroupStrategy())
                .registerWriteHandler(new GroupSheetHandler())
                .build();
        excelWriter.write(indexReadListener.getDataList(), indexSheet);

        // 写动态sheet数据
        Map<Integer, String> levelNames = indexReadListener.getLevelNames();
        for (int i = 0; i < headerSize; i++) {
            GroupDynamicReadListener dynamicReadListener = dynamicReadListenerMap.get(levelNames.get(i));
            // 设置sheet
            WriteSheet dynamicSheet = EasyExcelFactory.writerSheet(dynamicReadListener.getSheetName())
                    .head(UserGroupDynamicImportDTO.class)
                    .registerWriteHandler(ExcelUtil.buildGroupStrategy())
                    .registerWriteHandler(new GroupSheetHandler())
                    .build();
            excelWriter.write(dynamicReadListener.getDataList(), dynamicSheet);
        }
        excelWriter.finish();

        // 上传错误文件
        String uploadFileName = this.getErrorFileName(orgId);
        //String errorFilePath = ossService.uploadFile(outputStream.toByteArray(), uploadFileName, OssPathEnum.USER, "上传用户组织数据错误数据");
        String errorFilePath = storageService.upload(outputStream.toByteArray(), uploadFileName, OssPathEnum.USER.getPath(), new Dict()).getUrl();

        try {
            outputStream.close();
        } catch (IOException e) {
            log.error("上传用户组织数据错误数据, 关闭流失败");
        }
        return errorFilePath;
    }

    /**
     * 获取错误文件名称
     *
     * @param orgId
     * @return String
     * @author wangyunyun <yunyun.wang@idiaoyan.com>
     * @date 2022/4/20 15:49
     */
    private String getErrorFileName(String orgId) {
        OrganizationDTO organizationDTO = organizationService.selectByPrimaryKey(orgId).getData();
        // 上传导出文件 设置文件名称：如果公司名称存在则templateName为公司名称,公司名称为空则为orgId
        return String.format(ExcelConstant.USER_GROUP_UPLOAD_ERROR_NAME, organizationDTO != null && StringUtils.isNotBlank(organizationDTO.getName())
                ? organizationDTO.getName() : orgId);
    }

    /**
     * 校验数据,校验通过返回封装数据
     *
     * @param indexReadListener
     * @param dynamicReadListenerMap
     * @return
     */
    private List<UserGroupImportDTO> handleGroupExcelData(GroupIndexReadListener indexReadListener,
                                                          Map<String, GroupDynamicReadListener> dynamicReadListenerMap) {
        // 处理首页sheet数据
        // 获取层级名称集合
        Map<Integer, String> levelMap = indexReadListener.getLevelNames();
        int levelTotalCount = levelMap.size();

        // 获取组织行数据
        List<List<String>> indexDataList = indexReadListener.getDataList();
        String rootGroupName = indexDataList.get(0).get(0);

        // 设置组织编码map
        Map<String, Map<String, List<UserGroupDynamicImportDTO>>> levelAndGroupMap = new HashMap<>(16);
        for (Map.Entry<String, GroupDynamicReadListener> gruopEntry : dynamicReadListenerMap.entrySet()) {
            String levelName = gruopEntry.getKey();
            GroupDynamicReadListener groupDynamicReadListener = dynamicReadListenerMap.get(levelName);
            List<UserGroupDynamicImportDTO> groupImportDTOS = groupDynamicReadListener.getDataList();

            if (CollectionUtils.isNotEmpty(groupImportDTOS)) {
                Map<String, List<UserGroupDynamicImportDTO>> groupImportMap = groupImportDTOS.stream()
                        .collect(Collectors.groupingBy(UserGroupDynamicImportDTO::getGroupName));
                levelAndGroupMap.put(levelName, groupImportMap);
            }
        }

        // 遍历首页sheet数据
        List<UserGroupImportDTO> importDTOS = new ArrayList<>();
        Set<String> fullGroupNameSet = new HashSet<>();

        // 设置根组织
        importDTOS.add(buildRootGroup(rootGroupName, levelMap.get(0), levelAndGroupMap));

        // 设置非根组织
        for (List<String> rowData : indexDataList) {
            // 设置父组织名称集合
            List<String> fullParentGroupNames = new ArrayList<>();
            fullParentGroupNames.add(rootGroupName);

            // 遍历行的每列数据(从最小层级开始)
            for (int cloumnIndex = 1; cloumnIndex < levelTotalCount; cloumnIndex++) {
                String groupName = rowData.get(cloumnIndex);
                String fullGroupName = groupName;
                Integer groupStatus = UserGroupStatusEnum.ACTIVATED.getStatus();
                if (StringUtils.isBlank(groupName)) {
                    // 组织名称为空, 判断当前列是否是叶子列
                    if (isLeafColumnData(rowData, cloumnIndex, levelTotalCount)) {
                        continue;
                    }

                    // 名称为空, 且不是叶子列数据: 添加隐藏节点
                    groupName = getHiddenGroupName(rowData, cloumnIndex);
                    fullGroupName = MessageFormat.format("{0}*", groupName);
                    groupStatus = UserGroupStatusEnum.HIDDEN.getStatus();
                }

                String parentFullGroupNames = StringUtils.join(fullParentGroupNames, ",");
                fullParentGroupNames.add(fullGroupName);
                String fullGroupNames = StringUtils.join(fullParentGroupNames, ",");

                // 判断组织是否已经存在
                if (fullGroupNameSet.contains(fullGroupNames)) {
                    continue;
                }
                fullGroupNameSet.add(fullGroupNames);

                // 设置组织编码
                String groupCode = null;
                if (UserGroupStatusEnum.ACTIVATED.getStatus().equals(groupStatus)) {
                    Map<String, List<UserGroupDynamicImportDTO>> groupCodeMap = levelAndGroupMap.get(levelMap.get(cloumnIndex));
                    if (groupCodeMap != null) {
                        List<UserGroupDynamicImportDTO> groupImportDTOS = groupCodeMap.get(groupName);
                        if (CollectionUtils.isNotEmpty(groupImportDTOS)) {
                            UserGroupDynamicImportDTO groupImportDTO = groupImportDTOS.get(0);
                            groupCode = groupImportDTO.getGroupCode();

                            groupImportDTOS.remove(groupImportDTO);
                        }
                    }
                }

                // 设置参数
                UserGroupImportDTO importDTO = UserGroupImportDTO.builder()
                        .groupLevelName(levelMap.get(cloumnIndex))
                        .parentFullGroupName(parentFullGroupNames)
                        .fullGroupName(fullGroupNames)
                        .groupName(groupName)
                        .groupCode(groupCode)
                        .groupStatus(groupStatus)
                        .build();

                importDTOS.add(importDTO);
            }
        }

        return importDTOS;
    }

    /**
     * 设置根组织
     *
     * @param rootGroupName
     * @param rootGroupLevelName
     * @param levelAndGroupMap
     * @return
     */
    private UserGroupImportDTO buildRootGroup(String rootGroupName, String rootGroupLevelName,
                                              Map<String, Map<String, List<UserGroupDynamicImportDTO>>> levelAndGroupMap) {
        Map<String, List<UserGroupDynamicImportDTO>> groupCodeMap = levelAndGroupMap.get(rootGroupLevelName);
        List<UserGroupDynamicImportDTO> groupImportDTOS = groupCodeMap.get(rootGroupName);
        String rootGroupCode = null;
        if (CollectionUtils.isNotEmpty(groupImportDTOS)) {
            UserGroupDynamicImportDTO groupImportDTO = groupImportDTOS.get(0);
            rootGroupCode = groupImportDTO.getGroupCode();
        }

        return UserGroupImportDTO.builder()
                .groupLevelName(rootGroupLevelName)
                .parentFullGroupName(StringUtils.EMPTY)
                .fullGroupName(rootGroupName)
                .groupName(rootGroupName)
                .groupCode(rootGroupCode)
                .groupStatus(UserGroupStatusEnum.ACTIVATED.getStatus())
                .build();
    }

    /**
     * 判断当前列是否是叶子列
     *
     * @param rowData
     * @param cloumnIndex
     * @param levelTotalCount
     * @return
     */
    private boolean isLeafColumnData(List<String> rowData, int cloumnIndex, int levelTotalCount) {
        for (int i = cloumnIndex + 1; i < levelTotalCount; i++) {
            if (StringUtils.isNotBlank(rowData.get(i))) {
                return false;
            }
        }
        return true;
    }

    /**
     * 获取隐藏节点名称
     *
     * @param rowData
     * @param currentIndex
     * @return
     */
    private String getHiddenGroupName(List<String> rowData, int currentIndex) {
        String hiddenGroupName = null;
        for (int i = currentIndex; i >= 0; i--) {
            if (StringUtils.isNotBlank(rowData.get(i - 1))) {
                hiddenGroupName = rowData.get(i - 1);
                break;
            }
        }

        return hiddenGroupName;
    }

    /**
     * 读取首页sheet
     *
     * @param excelReader
     * @return
     */
    private GroupIndexReadListener readIndexSheet(ExcelReader excelReader) {
        // 设置对应的sheet
        String sheetName = ExcelConstant.USER_GROUP_EXCEL_FIRST_SHEET;
        // 设置监听器
        GroupIndexReadListener readTemplateListener = new GroupIndexReadListener();
        // 设置要读取的sheet的属性
        ReadSheet indexReadSheet = EasyExcelFactory
                .readSheet(sheetName)
                .registerReadListener(readTemplateListener)
                .build();
        // 读sheet
        excelReader.read(indexReadSheet);
        return readTemplateListener;
    }

    /**
     * 读取所有动态sheet
     *
     * @param excelReader
     * @param indexReadListener
     * @return
     */
    private ServiceResult<Map<String, GroupDynamicReadListener>> readDynamicSheet(ExcelReader excelReader,
                                                                                  GroupIndexReadListener indexReadListener) {
        Map<String, GroupDynamicReadListener> dynamicReadListenerMap = null;

        // 整体出错信息
        String wholeErrorMsg = null;
        if (!indexReadListener.hasReadError()) {
            // 首页sheet格式没有错误
            dynamicReadListenerMap = new HashMap<>(16);
            // 获取层级名称集合
            Map<Integer, String> headers = indexReadListener.getLevelNames();
            // 组织数量统计字段
            Integer totalGroupCount = NumberUtils.INTEGER_ZERO;
            // 设置是否需要校验动态sheet数据
            boolean hasDataError = indexReadListener.hasDataError();
            // 组织编码集合
            Set<String> groupCodes = new HashSet<>();
            for (Map.Entry<Integer, String> entry : headers.entrySet()) {
                String sheetName = entry.getValue();
                // 设置动态sheet监听器
                GroupDynamicReadListener dynamicReadListener =
                        new GroupDynamicReadListener(hasDataError, sheetName, totalGroupCount, groupCodes);
                readDynamicSheet(excelReader, dynamicReadListener, sheetName);
                if (dynamicReadListener.hasReadError()) {
                    // excel文件格式校验不通过,直接返回报错信息
                    log.error("sheet名称: {},格式有错误: {},读取失败.", dynamicReadListener.getSheetName(), dynamicReadListener.getErrorMsg());
                    wholeErrorMsg = dynamicReadListener.getErrorMsg();
                    return ServiceResult.fail(wholeErrorMsg);

                } else {
                    totalGroupCount = dynamicReadListener.getTotalGroupCount();
                    dynamicReadListenerMap.put(sheetName, dynamicReadListener);
                    hasDataError = dynamicReadListener.hasDataError();
                }
            }

            // 判断excel文件sheet数据格式校验是否通过
            if (indexReadListener.hasDataError() || hasDataError) {
                // excel文件sheet数据格式校验不通过,直接返回错误文件路径
                log.error("sheet数据格式有错误, 写错误文件.");
                ServiceResult<Map<String, GroupDynamicReadListener>> failResult = ServiceResult
                        .fail("sheet数据格式有错误, 写错误文件");
                failResult.setData(dynamicReadListenerMap);
                return failResult;
            }

        } else {
            // excel文件格式校验不通过,直接返回报错信息
            log.error("sheet名称: {},格式有错误: {},读取失败.", indexReadListener.getSheetName(), indexReadListener.getErrorMsg());
            wholeErrorMsg = indexReadListener.getErrorMsg();
            return ServiceResult.fail(wholeErrorMsg);
        }

        return ServiceResult.success(dynamicReadListenerMap);
    }

    /**
     * 读取动态sheet
     *
     * @param excelReader
     * @param readTemplateListener
     * @param sheetName
     */
    private void readDynamicSheet(ExcelReader excelReader, GroupDynamicReadListener readTemplateListener, String sheetName) {
        // 设置要读取的sheet的属性
        ReadSheet dynamicReadSheet = EasyExcelFactory
                .readSheet(sheetName)
                .head(UserGroupDynamicImportDTO.class)
                .registerReadListener(readTemplateListener)
                .build();
        List<ReadSheet> readSheetList = new ArrayList<>();
        readSheetList.add(dynamicReadSheet);
        // 读sheet
        excelReader.read(readSheetList);
    }

}
